#include "threads.h"
#include "ray.h"		// vector.h cascaded
//include "vector.h"
#include "pthread.h"
#include <cstdlib>	// malloc, free
#include <cstdio>	// printf, perror
#include <cassert>

void DoDrawByPixels(ThreadInfo* info)
{
	int x, y;
	Vector3 start = {0,0,0};
	Vector3 unitVector;
	int i;

	if (verbose)
		printf("DoDrawByPixels (%d -> %d) = %d pixels\n", info->x1, info->x2, info->x2 - info->x1);
	assert(SCREEN_WIDTH % 2 == 0);

	if ((info->scene == 1) || (info->scene == 2))
	{
		for (i = info->x1; i < info->x2; i++)
		{
			// assume pixels are in row-major ordering
			// viewport has upper-left corner = (0, 0)
			// start pixels in x1, end pixels in x2
			x = i % (SCREEN_WIDTH / 2);
			y = (int)((float)i / (float)(SCREEN_WIDTH / 2));
			
			// direction of camera
			unitVector[2] = SCREEN_WIDTH/3.14159265;
			unitVector[1] = -SCREEN_HEIGHT/2+y;
			unitVector[0] = -SCREEN_WIDTH/4+x;
			
			// normalize the vector to unit vector
			Normalize(unitVector);

			OutputBuffer[y*SCREEN_WIDTH+x + (info->scene == 1 ? 0 : SCREEN_WIDTH/2 )] = CastRay(info->scene == 1 ? Scene1 : Scene2, start, unitVector, 2);
		}
	}
}

void* DrawThreadByPixels(void *arg)
{
	ThreadInfo* info = (ThreadInfo *)arg;

	DoDrawByPixels(info);

	pthread_exit(0);
}

void DoDrawByBlock (ThreadInfo* info)
{
	int x, y;
	Vector3 start = {0,0,0}, unitVector;

	if (verbose)
		printf("DoDrawByBlock (y (%d -> %d), x (%d -> %d))\n", info->y1, info->y2, info->x1, info->x2);

	if (info->scene == 1 || info->scene == 2)
	{
		// go through the block of pixels and cast a ray for each pixel
		for (y = info->y1; y < info->y2; y++)
		{
			for (x = info->x1; x < info->x2; x++)
			{
				unitVector[2] = SCREEN_WIDTH/3.1415926;
				unitVector[1] = -SCREEN_HEIGHT/2+y;
				unitVector[0] = -SCREEN_WIDTH/4+x;		// 1/4 and 3/4 are the centers
				
				// normalize the vector to unit vector
				Normalize(unitVector);

				// add SCREEN_WIDTH / 2 to x depending on which scene to write to
				OutputBuffer[y*SCREEN_WIDTH+x + (info->scene == 1 ? 0 : SCREEN_WIDTH / 2 )] = CastRay(info->scene == 1 ? Scene1 : Scene2, start, unitVector, 2);
			}
		}
	}
}

void *DrawThreadByBlock (void *arg)
{
	ThreadInfo* info = (ThreadInfo *)arg;

	DoDrawByBlock(info);

	pthread_exit(0);
}

void SetupLUT(ThreadInfo*** threadLUT, int numThreads, int sceneId, enum ScreenPartitionType partMethod)
{
	int m = SCREEN_HEIGHT;
	int n = SCREEN_WIDTH / 2;	// two screens per scene
								// parameters for each scene are created the same
								// adjustments are made in DoDraw
	int i,j, tempThreads;
	ThreadInfo* info;

	int scenePixels;
	
	// setup LUT
	// everything is pass-by-value, even pointers
	(*threadLUT) = (ThreadInfo**)malloc(numThreads * sizeof(ThreadInfo*));
	info = (ThreadInfo*)malloc(numThreads * sizeof(ThreadInfo));		// contiguous
	if (!(*threadLUT) || !info)
	{
		perror("Thread LUT error");
		return;
	}
	for (i = 0; i < numThreads; i++)						// store the pointers
		(*threadLUT)[i] = info + i;

	switch (partMethod)
	{
		case BYPIXEL:
			scenePixels = m*n;		// height * (width / 2)
			tempThreads = (int)(scenePixels / numThreads);
			for (i = 0; i < numThreads - 1; i++)		// all the threads but last one
			{
				(*threadLUT)[i]->x1 = i * tempThreads;
				(*threadLUT)[i]->x2 = (i + 1) * tempThreads;
			}
			// the last pixel block might not have the same size
#ifdef DEBUG
			if ((i+1)*tempThreads != scenePixels)
				printf("Can't split scene evenly!\n");
#endif
			(*threadLUT)[i]->x1 = i*tempThreads;		// i == 0 if numThreads == 1
			(*threadLUT)[i]->x2 = scenePixels;
			break;
		case DEFAULT:
		case BYBLOCK:
		default:
			assert( ((m * n) % numThreads == 0) && (m*n >= numThreads));	// evenly divides
			if (numThreads <= m && (m % numThreads == 0))
			{
				// we can group together rows that calculate all the columns from 0 to n
				for (i = 0; i < numThreads; i++)
				{
					// x is columns, y is rows
					(*threadLUT)[i]->y1 = i * (m / numThreads);	// increment
					(*threadLUT)[i]->y2 = (i + 1) * (m / numThreads);
					(*threadLUT)[i]->x1 = 0;
					(*threadLUT)[i]->x2 = n;
				}
			}
			else if (numThreads <= n && (n % numThreads == 0))
			{
				// split the columns into groups
				for (i = 0; i < numThreads; i++)
				{
					// x is columns, y is rows
					(*threadLUT)[i]->y1 = 0;
					(*threadLUT)[i]->y2 = m;
					(*threadLUT)[i]->x1 = i * (n / numThreads);
					(*threadLUT)[i]->x2 = (i + 1) * (n / numThreads);
				}
			}
			else
			{
				tempThreads = numThreads / m;
				// we go through all rows, and the columns are split as well
				for (i = 0; i < m; i++)
					for (j = 0; j < tempThreads; j++)
					{
						assert((i*tempThreads+j) < numThreads);
						// example:  for 128x128 with 256
						// numThreads/(m = 128 rows) = 2 columns per row = tempThreads
						// n/tempThreads is the amount to increment per column

						(*threadLUT)[i*tempThreads+j]->y1 = i;
						(*threadLUT)[i*tempThreads+j]->y2 = (i + 1);
						(*threadLUT)[i*tempThreads+j]->x1 = j * (n / tempThreads);
						(*threadLUT)[i*tempThreads+j]->x2 = (j + 1) * (n / tempThreads);
					}
			}
			break;
	}

	for (i = 0; i < numThreads; i++)
	{
		(*threadLUT)[i]->scene = sceneId;
		//printf(" (%d,%d) (%d,%d) \n", threadLUT[i]->y1, threadLUT[i]->x1, threadLUT[i]->y2, threadLUT[i]->x2);
	}
}

void MakeThreads (int numThreads, int sceneId, enum ScreenPartitionType partMethod)
{
	// m x n screen matrix
	int i,j;
	ThreadInfo** threadLUT;

	pthread_t* threadIds;
	static void* (*DrawThreadLUT[])(void*) = { DrawThreadByBlock,		// DEFAULT ( = 0)
												DrawThreadByBlock,		// BYBLOCK
												DrawThreadByPixels };	// BYPIXEL
	void* (*DrawThread)(void*) = DrawThreadLUT[(int)partMethod];	// get the corresponding DrawThread
	
	SetupLUT(&threadLUT, numThreads, sceneId, partMethod);

	// setup threadIds table
	threadIds = (pthread_t*)malloc(numThreads*sizeof(pthread_t));
	if (!threadIds)
	{
		perror("malloc on threadIds");
		return;
	}

	// create the threads!
	for (i = 0; i < numThreads; i++)
	{
		j = pthread_create(&threadIds[i], NULL, DrawThread, threadLUT[i]);
			// NULL for default pthread_attrs
		if (j)		// status
			perror("pthread_create error");

	}

	// join all 
	for (i = 0; i < numThreads; i++)
		pthread_join(threadIds[i], NULL);

	free(threadIds);
	free(threadLUT[0]);		// free contiguous space
	free(threadLUT);		// then the pointer array
}

